import IndexOutOfBoundException from "../exceptions/IndexOutOfBoundException";
import Collection from "../core/Collection";
import {equalsTo} from "../core/Obj";
import IllegalArgumentException from "../exceptions/IllegalArgumentException";
import FullCapacityException from "../exceptions/FullCapacityException";
import ComparableObj from "../core/ComparableObj";
import Comparator, {compareTo} from "./Comparator";

/**
 * 一些静态的，针对{@link Collection}的方法。
 *
 * @version 1.0
 */
export default class Collections {
  /**
   * 对给定的数组，查询给定的索引是否符合要求。
   *
   * 由于javascript不支持整形，因此会先对数字进行取证操作。
   * 如果索引 < 0 ，则会尝试使用{@link Collection#size} 加上当前索引以满足倒叙的目的。
   *
   * 如果在上述操作完成后依然 < 0 或 > size, 则会抛出{@link IndexOutOfBoundException}异常.
   *
   * @param collection 需要查询的数组
   * @param index 需要查询的索引
   * @return {number} 经过修正的索引
   * @throws {IndexOutOfBoundException} 当索引超出数组范围中
   * @version 1.0
   */
  public static accurateIndex(collection: Collection<any>, index: number): number {
    index = Math.round(index);
    if (index < 0) {
      index = collection.size() + index;
    }
    if (index >= collection.size() || index < 0) {
      throw new IndexOutOfBoundException(index, collection.size() - 1);
    }
    return index;
  }

  /**
   * 使用指定的元素，填满一个全新的集合。
   *
   * 若需要加入的集合无法容纳这些元素时，将抛出{@link FullCapacityException}异常，拷贝方法不会被执行。
   *
   * 若此集合的add方法不被支持，则会抛出{@link UnsupportedOperationException}
   *
   * @param collection 需要将元素加入的集合
   * @param e 需要加入的元素
   * @param length 需要加入的次数
   * @throws {IllegalArgumentException} 给出的加入次数小于0
   * @throws {FullCapacityException} 集合无法容纳更多的元素
   * @throws {UnsupportedOperationException} add方法不被此集合支持
   * @version 1.0
   */
  public static fill<E>(collection: Collection<E>, e: E, length: number): void {

    if (length < 0) {
      throw new IllegalArgumentException("length " + length + " should be bigger than 0");
    }

    if (length + collection.size() > collection.capacity()) {
      throw new FullCapacityException("collection", collection.capacity());
    }

    for (let i = 0; i < length; i++) {
      collection.add(e);
    }
  }

  /**
   * 将一个集合中的所有元素进行倒序排列。
   *
   * @param collection 需要被倒序的集合
   * @throws {UnsupportedOperationException} add和clear方法不被此集合支持
   * @version 1.0
   */
  public static reverse<E>(collection: Collection<E>): void {
    const stream = collection.toArray();
    collection.clear();
    for (let i = stream.length - 1; i >= 0; i--) {
      collection.add(stream[i]);
    }
  }

  /**
   * 对一个集合进行拷贝，并将其加入到另一个集合中。
   *
   * 拷贝将执行浅拷贝，对对象的深层操作依然会对结果产生影响。
   *
   * 若需要加入的集合无法容纳这些元素时，将抛出{@link Collection#add}异常，拷贝方法不会被执行。
   *
   * @param origin 需要被拷贝的集合
   * @param result 需要将拷贝内容加入到的集合
   * @throws {FullCapacityException} 集合无法容纳更多的元素
   * @throws {UnsupportedOperationException} add方法和clear方法不被此集合支持
   * @version 1.0
   */
  public static copy<E>(origin: Collection<E>, result: Collection<E>): void {
    if (origin.size() + result.size() > result.capacity()) {
      throw new FullCapacityException("collection " + result + " doesn't have enough capacity");
    }

    result.clear();
    for (let element of origin) {
      result.add(element);
    }
  }

  /**
   * 通过依次对两个集合进行比较，判断这两个集合是否相等。
   *
   * 如果两个集合长度相等，则进行依次比较。否则直接判定为不相等。
   *
   * 其他影响，如集合的实现类型、集合的容量等不会成为参考因素。
   *
   * @param a 第一个集合
   * @param b 第二个集合
   * @return {boolean} 两个集合是否相等
   * @version 1.0
   */
  public static equals<E>(a: Collection<E>, b: Collection<E>): boolean {
    if (a.size() !== b.size()) {
      return false;
    } else {
      let flag = true;
      const iteratorA = a.iterator();
      const iteratorB = b.iterator();
      let elementA: IteratorResult<E>;
      let elementB: IteratorResult<E>;
      do {
        elementA = iteratorA.next();
        elementB = iteratorB.next();
        if (!equalsTo(elementA.value, elementB.value)) {
          flag = false;
          break;
        }
      } while (elementA.done || elementB.done);
      return flag;
    }
  }

  /**
   * 从一个集合中，选取一个最小值。
   *
   * 要求该集合的元素必须继承自{@link ComparableObj}。方法将使用其中的比较器进行比较。
   *
   * @param collection 需要选择最小值的集合
   * @return {E | undefined} 返回集合中的最小值，或者undefined当集合中没有元素
   * @see ComparableObj
   * @version 1.0
   */
  public static min<E extends ComparableObj<E>>(collection: Collection<E>): E | undefined {
    let flag = undefined;
    const iterator = collection.iterator();
    let element;
    do {
      element = iterator.next();
      if (compareTo(element.value, flag) < 0) {
        flag = element.value;
      }
    } while (element.done);
    return flag;
  }

  /**
   * 从一个集合中，选取一个最小值。
   *
   * 此方法不同于{@link Collections#min}，它不需要元素继承{@link ComparableObj}，而是额外需要一个{@link Comparator}。
   *
   * @param collection 需要选择最小值的集合
   * @param comparator 元素的比较器
   * @return {E | undefined} 返回集合中的最小值，或者undefined当集合中没有元素
   * @see Comparator
   * @version 1.0
   */
  public static minByComparator<E>(collection: Collection<E>, comparator: Comparator): E | undefined {
    let flag = undefined;
    const iterator = collection.iterator();
    let element;
    do {
      element = iterator.next();
      if (comparator(element.value, flag) < 0) {
        flag = element.value;
      }
    } while (element.done);
    return flag;
  }

  /**
   * 从一个集合中，选取一个最大值。
   *
   * 要求该集合的元素必须继承自{@link ComparableObj}。方法将使用其中的比较器进行比较。
   *
   * @param collection 需要选择最大值的集合
   * @return {E | undefined} 返回集合中的最大值，或者undefined当集合中没有元素
   * @see ComparableObj
   * @version 1.0
   */
  public static max<E extends ComparableObj<E>>(collection: Collection<E>): E | undefined {
    let flag = undefined;
    const iterator = collection.iterator();
    let element;
    do {
      element = iterator.next();
      if (compareTo(element.value, flag) > 0) {
        flag = element.value;
      }
    } while (element.done);
    return flag;
  }

  /**
   * 从一个集合中，选取一个最大值。
   *
   * 此方法不同于{@link Collections#min}，它不需要元素继承{@link ComparableObj}，而是额外需要一个{@link Comparator}。
   *
   * @param collection 需要选择最大值的集合
   * @param comparator 元素的比较器
   * @return {E | undefined} 返回集合中的最大值，或者undefined当集合中没有元素
   * @see Comparator
   * @version 1.0
   */
  public static maxByComparator<E>(collection: Collection<E>, comparator: Comparator): E | undefined {
    let flag = undefined;
    const iterator = collection.iterator();
    let element;
    do {
      element = iterator.next();
      if (comparator(element.value, flag) > 0) {
        flag = element.value;
      }
    } while (element.done);
    return flag;
  }
}
